<?php

class Benc {

	public static function encode($obj) {
		if (!is_array($obj) || !isset($obj["type"]) || !isset($obj["value"]))
			return;
		$c = $obj["value"];
		switch ($obj["type"]) {
			case "string":
				return self::benc_str($c);
			case "integer":
				return self::benc_int($c);
			case "list":
				return self::benc_list($c);
			case "dictionary":
				return self::benc_dict($c);
			default:
				return;
		}
	}

	public static function benc_str($s) {
		return strlen($s) . ":$s";
	}

	public static function benc_int($i) {
		return "i" . $i . "e";
	}

	public static function benc_list($a) {
		$s = "l";
		foreach ($a as $e) {
			$s .= self::encode($e);
		}
		$s .= "e";
		return $s;
	}

	public static function benc_dict($d) {
		$s = "d";
		$keys = array_keys($d);
		sort($keys);
		foreach ($keys as $k) {
			$v = $d[$k];
			$s .= self::benc_str($k);
			$s .= self::encode($v);
		}
		$s .= "e";
		return $s;
	}

	public static function decode_file($f, $ms) {
		$fp = fopen($f, "rb");
		if (!$fp)
			return;
		$e = fread($fp, $ms);
		fclose($fp);
		return self::decode($e);
	}

	public static function decode($s) {
		if (preg_match('/^(\d+):/', $s, $m)) {
			$l = $m[1];
			$pl = strlen($l) + 1;
			$v = substr($s, $pl, $l);
			$ss = substr($s, 0, $pl + $l);
			if (strlen($v) != $l)
				return;
			return array('type' => "string", 'value' => $v, 'strlen' => strlen($ss), 'string' => $ss);
		}
		if (preg_match('/^i(\d+)e/', $s, $m)) {
			$v = $m[1];
			$ss = "i" . $v . "e";
			if ($v === "-0")
				return;
			if ($v[0] == "0" && strlen($v) != 1)
				return;
			return array('type' => "integer", 'value' => $v, 'strlen' => strlen($ss), 'string' => $ss);
		}
		switch ($s[0]) {
			case "l":
				return self::decode_list($s);
			case "d":
				return self::decode_dict($s);
			default:
				return;
		}
	}

	public static function decode_list($s) {
		if ($s[0] != "l")
			return;
		$sl = strlen($s);
		$i = 1;
		$v = array();
		$ss = "l";
		for (;;) {
			if ($i >= $sl)
				return;
			if ($s[$i] == "e")
				break;
			$ret = self::decode(substr($s, $i));
			if (!isset($ret) || !is_array($ret))
				return;
			$v[] = $ret;
			$i += $ret["strlen"];
			$ss .= $ret["string"];
		}
		$ss .= "e";
		return array('type' => "list", 'value' => $v, 'strlen' => strlen($ss), 'string' => $ss);
	}

	public static function decode_dict($s) {
		if ($s[0] != "d")
			return;
		$sl = strlen($s);
		$i = 1;
		$v = array();
		$ss = "d";
		for (;;) {
			if ($i >= $sl)
				return;
			if ($s[$i] == "e")
				break;
			$ret = self::decode(substr($s, $i));
			if (!isset($ret) || !is_array($ret) || $ret["type"] != "string")
				return;
			$k = $ret["value"];
			$i += $ret["strlen"];
			$ss .= $ret["string"];
			if ($i >= $sl)
				return;
			$ret = self::decode(substr($s, $i));
			if (!isset($ret) || !is_array($ret))
				return;
			$v[$k] = $ret;
			$i += $ret["strlen"];
			$ss .= $ret["string"];
		}
		$ss .= "e";
		return array('type' => "dictionary", 'value' => $v, 'strlen' => strlen($ss), 'string' => $ss);
	}

	public static function dict_check($d, $s) {
		if ($d["type"] != "dictionary")
			die("not a dictionary");
		$a = explode(":", $s);
		$dd = $d["value"];
		$ret = array();
		$t='';
		foreach ($a as $k) {
			unset($t);
			if (preg_match('/^(.*)\((.*)\)$/', $k, $m)) {
				$k = $m[1];
				$t = $m[2];
			}
			if (!isset($dd[$k]))
				die("dictionary is missing key(s)");
			if (isset($t)) {
				if ($dd[$k]["type"] != $t)
					die("invalid entry in dictionary");
				$ret[] = $dd[$k]["value"];
			}
			else
				$ret[] = $dd[$k];
		}
		return $ret;
	}

	public static function dict_get($d, $k, $t) {
		if ($d["type"] != "dictionary")
			die("not a dictionary");
		$dd = $d["value"];
		if (!isset($dd[$k]))
			return null;
		$v = $dd[$k];
		if ($v["type"] != $t)
			die("invalid dictionary entry type");
		return $v["value"];
	}

	public static function dict_replace(&$dict, $key, $type, $newvalue) {
		$newstr = self::encode(array('type' => $type, 'value' => $newvalue));

		$dict['value'][$key] = array(
			'type' => $type,
			'value' => $newvalue,
			'strlen' => strlen($newstr),
			'string' => $newstr,
		);
	}

}
?>
